Files
sdk/runtime/vm/compiler/aot/precompiler.cc
T
Ryan Macnak d36adbacaf [vm] Remove the VM isolate.
The former contents of the VM isolate are now included into each isolate group. This makes each isolate group's heap independent, and in particular allows each heap to be allocated to a separate pointer cage (not done in this CL).

The duplicated stubs that allowed PC relative calls are removed, since the originals can now be the target of PC relative calls.

The bootstrapping needing to load an AppJIT or AppAOT snapshot is reduced to allocating the oddballs. The code is entirely dropped in the AOT runtime, but the JIT runtime still has it to allow for flags to affect the compilation of the stub code. Further refactoring might be able to remove this for the JIT runtime too, with only gen_snapshot knowing how to bootstrap.

Class serialization no longer distinguishes predefined classes.

The page containing null is marked as never-evacuate. null, false and true must not move because the compiler relies on their low bits having certain patterns for some optimizations. (Previously, the entire VM isolate heap never moved.)

Compaction is disabled for IA32. Due to register pressure, some stub calls must not use a scratch register and embed the address of Code.

The page containing the call-through-safepoint stub is frozen when running with --write-protect-code and the stub is created at runtime (instead of loaded from an AppJIT or AppAOT snapshot). This stub must remain executable even during a safepoint, as a foreign call might during return during a safepoint and only block after the stub directs it to the runtime.

The snapshot symbols are renamed to kDartSnapshotData and kDartSnapshotText. There is no need to distinguish the VM isolate's snapshot, and snaphots are per isolate group not per isolate. Aliases with the old names are added to ease migration.

Some global flags that were automatically set based on the VM isolate's snapshot are now isolate group flags and automatically set by the isolate group's snapshot.

TEST=ci
Change-Id: Iee82016057d609112e9b021d178fc3d4d18b5044
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500621
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Tess Strickland <sstrickl@google.com>
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
2026-05-18 11:35:03 -07:00

4072 lines
144 KiB
C++

// 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 <memory>
#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<RetainedReasonsTrait>;
void WriteRetainedObjectSpecificFields(const Object& obj) {
if (obj.IsFunction()) {
writer_.PrintProperty("type", "Function");
const auto& function = Function::Cast(obj);
writer_.PrintProperty("name",
function.ToLibNamePrefixedQualifiedCString());
writer_.PrintProperty("kind",
UntaggedFunction::KindToCString(function.kind()));
return;
} else if (obj.IsFunctionType()) {
writer_.PrintProperty("type", "FunctionType");
const auto& sig = FunctionType::Cast(obj);
writer_.PrintProperty("name", sig.ToCString());
return;
}
FATAL("Unexpected object %s", obj.ToCString());
}
Zone* const zone_;
RetainedReasonsMap retained_reasons_map_;
JSONWriter writer_;
void* file_;
};
class PrecompileParsedFunctionHelper : public ValueObject {
public:
PrecompileParsedFunctionHelper(Precompiler* precompiler,
ParsedFunction* parsed_function)
: 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<FunctionSet>(/*initial_capacity=*/1024)),
functions_with_entry_point_pragmas_(
HashTables::New<FunctionSet>(/*initial_capacity=*/1024)),
seen_functions_(HashTables::New<FunctionSet>(/*initial_capacity=*/1024)),
possibly_retained_functions_(
HashTables::New<FunctionSet>(/*initial_capacity=*/1024)),
fields_to_retain_(),
functions_to_retain_(
HashTables::New<FunctionSet>(/*initial_capacity=*/1024)),
classes_to_retain_(),
typeargs_to_retain_(),
types_to_retain_(),
functiontypes_to_retain_(),
typeparams_to_retain_(),
consts_to_retain_(),
seen_table_selectors_(),
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<intptr_t> cids;
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
HANDLESCOPE(T);
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
while (it.HasNext()) {
cls = it.GetNextClass();
if (!cls.is_allocated()) continue;
fields = cls.fields();
for (intptr_t k = 0; k < fields.Length(); k++) {
field ^= fields.At(k);
if (field.is_static()) continue;
field_type = field.type();
if (!field_type.IsFunctionType()) continue;
field_name = field.name();
if (!IsSent(field_name)) continue;
// Create arguments descriptor with fixed parameters from
// signature of field_type.
signature ^= field_type.ptr();
if (signature.IsGeneric()) continue;
if (signature.HasOptionalParameters()) continue;
if (FLAG_trace_precompiler) {
THR_Print("Found callback field %s\n", field_name.ToCString());
}
// TODO(dartbug.com/33549): Update this code to use the size of the
// parameters when supporting calls to non-static methods with
// unboxed parameters.
args_desc =
ArgumentsDescriptor::NewBoxed(0, // No type argument vector.
signature.num_fixed_parameters());
cids.Clear();
if (CHA::ConcreteSubclasses(cls, &cids)) {
for (intptr_t j = 0; j < cids.length(); ++j) {
subcls = IG->class_table()->At(cids[j]);
if (subcls.is_allocated()) {
// Add dispatcher to cls.
dispatcher = subcls.GetInvocationDispatcher(
field_name, args_desc,
UntaggedFunction::kInvokeFieldDispatcher,
/* create_if_absent = */ true,
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<Code::kSCallTableFunctionTarget>();
if (entry.IsFunction()) {
// Since generally function objects are retained when symbolic stack
// traces are enabled, only return kForcedRetain to mark that retention
// was otherwise forced.
const char* const reason =
FLAG_retain_function_objects
? (!FLAG_dwarf_stack_traces_mode
? RetainReasons::kSymbolicStackTraces
: RetainReasons::kForcedRetain)
: nullptr;
AddFunction(Function::Cast(entry), reason);
ASSERT(view.Get<Code::kSCallTableCodeOrTypeTarget>() == Code::null());
continue;
}
entry = view.Get<Code::kSCallTableCodeOrTypeTarget>();
if (entry.IsCode() && Code::Cast(entry).IsAllocationStubCode()) {
cls ^= Code::Cast(entry).owner();
AddInstantiatedClass(cls);
}
}
const ExceptionHandlers& handlers =
ExceptionHandlers::Handle(Z, code.exception_handlers());
if (!handlers.IsNull()) {
#if defined(PRODUCT)
// List of handled types is only used by debugger and
// can be removed in PRODUCT mode.
for (intptr_t i = 0; i < handlers.num_entries(); i++) {
handlers.SetHandledTypes(i, Array::empty_array());
}
#else
Array& types = Array::Handle(Z);
AbstractType& type = AbstractType::Handle(Z);
for (intptr_t i = 0; i < handlers.num_entries(); i++) {
types = handlers.GetHandledTypes(i);
for (intptr_t j = 0; j < types.Length(); j++) {
type ^= types.At(j);
AddType(type);
}
}
#endif // defined(PRODUCT)
}
#if defined(TARGET_ARCH_IA32)
FATAL("Callee scanning unimplemented for IA32");
#endif
String& selector = String::Handle(Z);
// When tracing we want to scan the object pool attached to the code object
// rather than scanning global object pool - because we want to include
// *all* outgoing references into the trace. Scanning GOP would exclude
// references that have been deduplicated.
if (!is_tracing()) {
for (intptr_t i = gop_offset;
i < global_object_pool_builder()->CurrentLength(); i++) {
const auto& wrapper_entry = global_object_pool_builder()->EntryAt(i);
if (wrapper_entry.type() ==
compiler::ObjectPoolBuilderEntry::kTaggedObject) {
const auto& entry = *wrapper_entry.obj_;
AddCalleesOfHelper(entry, &selector, &cls);
}
}
} else {
const auto& pool = ObjectPool::Handle(Z, code.object_pool());
auto& entry = Object::Handle(Z);
for (intptr_t i = 0; i < pool.Length(); i++) {
if (pool.TypeAt(i) == ObjectPool::EntryType::kTaggedObject) {
entry = pool.ObjectAt(i);
AddCalleesOfHelper(entry, &selector, &cls);
}
}
}
if (!FLAG_dwarf_stack_traces_mode) {
const Array& inlined_functions =
Array::Handle(Z, code.inlined_id_to_function());
for (intptr_t i = 0; i < inlined_functions.Length(); i++) {
target ^= inlined_functions.At(i);
AddRetainReason(target, RetainReasons::kSymbolicStackTraces);
AddTypesOf(target);
}
}
}
static bool IsPotentialClosureCall(const String& selector) {
return selector.ptr() == Symbols::call().ptr() ||
selector.ptr() == Symbols::DynamicCall().ptr();
}
void Precompiler::AddCalleesOfHelper(const Object& entry,
String* temp_selector,
Class* temp_cls) {
switch (entry.GetClassId()) {
case kOneByteStringCid:
case kNullCid:
// Skip common leaf constants early in order to
// process object pools faster.
return;
case kUnlinkedCallCid: {
const auto& call_site = UnlinkedCall::Cast(entry);
// A dynamic call.
*temp_selector = call_site.target_name();
*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(&param)) 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<class Instance*>& subinstances() const {
return subinstances_;
}
private:
Object& subinstance_;
GrowableArray<class Instance*> 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<NameFunctionsTraits> Table;
static void AddNameToFunctionsTable(Zone* zone,
Table* table,
const String& fname,
const Function& function) {
Array& farray = Array::Handle(zone);
farray ^= table->InsertNewOrGetValue(fname, Array::empty_array());
farray = Array::Grow(farray, farray.Length() + 1);
farray.SetAt(farray.Length() - 1, function);
table->UpdateValue(fname, farray);
}
static void AddNamesToFunctionsTable(Zone* zone,
Table* table,
const String& fname,
const Function& function,
String* mangled_name,
Function* dyn_function) {
AddNameToFunctionsTable(zone, table, fname, function);
*dyn_function = function.ptr();
if (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<Table>(100));
for (intptr_t i = 0; i < libraries_.Length(); i++) {
lib ^= libraries_.At(i);
HANDLESCOPE(T);
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
while (it.HasNext()) {
cls = it.GetNextClass();
functions = cls.current_functions();
const intptr_t length = functions.Length();
for (intptr_t j = 0; j < length; j++) {
function ^= functions.At(j);
if (function.IsDynamicFunction()) {
fname = function.name();
if (function.IsSetterFunction() ||
function.IsImplicitSetterFunction()) {
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
} else if (function.IsGetterFunction() ||
function.IsImplicitGetterFunction()) {
// Enter both getter and non getter name.
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
fname = Field::NameFromGetter(fname);
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
} else if (function.IsMethodExtractor()) {
// Skip. We already add getter names for regular methods below.
continue;
} else {
// Regular function. Enter both getter and non getter name.
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
fname = Field::GetterName(fname);
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
}
}
}
}
}
// Locate all entries with one function only
Table::Iterator iter(&table);
String& key = String::Handle(Z);
String& key_demangled = String::Handle(Z);
UniqueFunctionsMap functions_map(HashTables::New<UniqueFunctionsMap>(20));
while (iter.MoveNext()) {
intptr_t curr_key = iter.Current();
key ^= table.GetKey(curr_key);
farray ^= table.GetOrNull(key);
ASSERT(!farray.IsNull());
if (farray.Length() == 1) {
function ^= farray.At(0);
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<FunctionSet>(/*initial_capacity=*/1024));
auto& code = Code::Handle(Z);
auto& function = Function::Handle(Z);
for (intptr_t i = 0; i < entries.Length(); i++) {
code = Code::RawCast(entries.At(i));
if (code.IsNull()) continue;
if (!code.IsFunctionCode()) continue;
function = code.function();
ASSERT(!function.IsNull());
if (printed.ContainsKey(function)) continue;
if (functions_to_retain_.ContainsKey(function)) continue;
THR_Print("Dispatch table references code for function to drop: %s\n",
function.ToLibNamePrefixedQualifiedCString());
printed.Insert(function);
}
printed.Release();
}
void Precompiler::ReplaceFunctionStaticCallEntries() {
PRECOMPILER_TIMER_SCOPE(this, ReplaceFunctionStaticCallEntries);
class StaticCallTableEntryFixer : public CodeVisitor {
public:
explicit StaticCallTableEntryFixer(Zone* zone)
: table_(Array::Handle(zone)),
kind_and_offset_(Smi::Handle(zone)),
target_function_(Function::Handle(zone)),
target_code_(Code::Handle(zone)),
pool_(ObjectPool::Handle(zone)) {}
void VisitCode(const Code& code) {
if (!code.IsFunctionCode()) return;
table_ = code.static_calls_target_table();
StaticCallsTable static_calls(table_);
// With bare instructions, there is a global pool and per-Code local
// pools. Instructions are generated to use offsets into the global pool,
// but we still use the local pool to track which Code are using which
// pool values for the purposes of analyzing snapshot size
// (--write_v8_snapshot_profile_to and --print_instructions_sizes_to) and
// deferred loading deciding which snapshots to place pool values in.
// We don't keep track of which offsets in the local pools correspond to
// which entries in the static call table, so we don't properly replace
// the old references to the CallStaticFunction stub, but it is sufficient
// for the local pool to include the actual call target.
compiler::ObjectPoolBuilder builder;
pool_ = code.object_pool();
pool_.CopyInto(&builder);
for (auto& view : static_calls) {
kind_and_offset_ = view.Get<Code::kSCallTableKindAndOffset>();
auto const kind = Code::KindField::decode(kind_and_offset_.Value());
if ((kind != Code::kCallViaCode) && (kind != Code::kPcRelativeCall))
continue;
target_function_ = view.Get<Code::kSCallTableFunctionTarget>();
if (target_function_.IsNull()) continue;
ASSERT(view.Get<Code::kSCallTableCodeOrTypeTarget>() == Code::null());
ASSERT(target_function_.HasCode());
target_code_ = target_function_.CurrentCode();
ASSERT(!target_code_.IsStubCode());
view.Set<Code::kSCallTableCodeOrTypeTarget>(target_code_);
view.Set<Code::kSCallTableFunctionTarget>(Object::null_function());
if (kind == Code::kCallViaCode) {
auto const pc_offset =
Code::OffsetField::decode(kind_and_offset_.Value());
const uword pc = pc_offset + code.PayloadStart();
CodePatcher::PatchStaticCallAt(pc, code, target_code_);
builder.AddObject(Object::ZoneHandle(target_code_.ptr()));
}
if (FLAG_trace_precompiler) {
THR_Print("Updated static call entry to %s in \"%s\"\n",
target_function_.ToFullyQualifiedCString(),
code.ToCString());
}
}
code.set_object_pool(ObjectPool::NewFromBuilder(builder));
}
private:
Array& table_;
Smi& kind_and_offset_;
Function& target_function_;
Code& target_code_;
ObjectPool& pool_;
};
HANDLESCOPE(T);
StaticCallTableEntryFixer visitor(Z);
ProgramVisitor::WalkProgram(Z, IG, &visitor);
}
void Precompiler::DropFunctions() {
HANDLESCOPE(T);
Library& lib = Library::Handle(Z);
Class& cls = Class::Handle(Z);
Array& functions = Array::Handle(Z);
Function& function = Function::Handle(Z);
Function& target = Function::Handle(Z);
Function& implicit_closure = Function::Handle(Z);
Code& code = Code::Handle(Z);
Object& owner = Object::Handle(Z);
GrowableObjectArray& retained_functions = GrowableObjectArray::Handle(Z);
auto& sig = FunctionType::Handle(Z);
auto& ref = Object::Handle(Z);
auto trim_function = [&](const Function& function) {
if (function.IsDynamicInvocationForwarder()) {
// For dynamic invocation forwarders sever strong connection between the
// forwarder and the target function if we are not going to retain
// target function anyway. The only use of the forwarding target outside
// of compilation pipeline is in Function::script() and that should not
// be used when we are dropping functions (cause we are not going to
// emit symbolic stack traces anyway).
// Note that we still need Function::script() to work during snapshot
// generation to generate DWARF, that's why we are using WSR and not
// simply setting forwarding target to null.
target = function.ForwardingTarget();
if (!functions_to_retain_.ContainsKey(target)) {
ref =
WeakSerializationReference::New(target, Function::null_function());
function.set_data(ref);
}
}
sig = function.signature();
// In the AOT runtime, most calls are direct or through the dispatch table,
// not resolved via dynamic lookup. Thus, we only need to retain the
// function signature in the following cases:
if (function.IsClosureFunction()) {
// Dynamic calls to closures go through dynamic closure call dispatchers,
// which need the signature.
return AddRetainReason(sig, RetainReasons::kClosureSignature);
}
if (function.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<DispatcherSet>(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<const AbstractType> types(Z, 200);
{
class TypesCollector : public ObjectVisitor {
public:
explicit TypesCollector(Zone* zone,
GrowableHandlePtrArray<const AbstractType>* types)
: type_(AbstractType::Handle(zone)), types_(types) {}
void VisitObject(ObjectPtr obj) override {
const auto cid = obj->GetClassIdOfHeapObject();
if (cid == kTypeCid || cid == kFunctionTypeCid ||
cid == kRecordTypeCid) {
type_ ^= obj;
types_->Add(type_);
}
}
private:
AbstractType& type_;
GrowableHandlePtrArray<const AbstractType>* types_;
};
HeapIterationScope his(T);
TypesCollector visitor(Z, &types);
// Find all type objects in this isolate.
IG->heap()->VisitObjects(&visitor);
}
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<ConstantVisitedValue>(
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<ConstantVisitedValue>(
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<ConstantVisitedValue>(
visited_->GetValueExclusive(element)) == kDrop) {
visited_->SetValueExclusive(object_ptr, kDrop);
break;
}
element = iterator.CurrentValue();
Visit(element);
if (static_cast<ConstantVisitedValue>(
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<ConstantVisitedValue>(
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<WeakTable> 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<CanonicalInstanceTraits> 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<ConstantVisitedValue>(
visited->GetValueExclusive(obj.ptr()));
if (value == kNotVisited) {
visitor.Visit(obj.ptr());
value = static_cast<ConstantVisitedValue>(
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<Code::kSCallTableKindAndOffset>();
auto const kind = Code::KindField::decode(kind_and_offset_.Value());
if (kind == Code::kCallViaCode) {
call_target_ =
Code::RawCast(view.Get<Code::kSCallTableCodeOrTypeTarget>());
ASSERT(!call_target_.IsNull());
targets_of_calls_via_code_.Add(call_target_);
}
}
}
void VisitCode(const Code& code) override {
++total_code_objects_;
RecordCodeObjectsUsedForCalls(code);
// 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<CodeKeyTraits> CodeSet;
#if defined(DEBUG)
FunctionPtr Precompiler::FindUnvisitedRetainedFunction() {
class CodeChecker : public CodeVisitor {
public:
CodeChecker()
: visited_code_(HashTables::New<CodeSet>(/*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<const Script>* 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<const Script>* scripts_;
};
GrowableHandlePtrArray<const Script> 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<const compiler::TableSelector*>& 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<const ICData*>* ic_data_array =
new (zone) ZoneGrowableArray<const ICData*>();
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<ObfuscationMap>(
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<uint8_t*>(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